vue实现多行文本展开收起组件

您所在的位置:网站首页 vue 动态列表展开收起 vue实现多行文本展开收起组件

vue实现多行文本展开收起组件

2023-06-18 14:56| 来源: 网络整理| 查看: 265

一、内容简介

多行文本展开收起功能现在已经算是一个非常常见的效果了,之前写过一篇文章也是用vue实现了文本展开收起功能。一年过去了,是时候展示真正的技术了^ _ ^。这次实现一个效果更好的组件:

效果预览:

二、实现思路

getClientRects: 先来看下这个api可以得到什么

{{ text }} export default { data() { return { text: 'xxxxx' } }, mounted() { console.log('overEllipsis', this.$refs.overEllipsis.getClientRects()) } } 复制代码

getClientRects 可以获取到多行文本的宽高位置等属性,可以通过getClientRects来判断当前文本的内容总行数和每行宽度。只要不断截取文本,判断总行数,直到行数符合预期后,展示截取后的文案。

三、实现组件

思路有了接下来要开始敲代码实现功能了。先要确定这个组件的传参和功能,为了使用简单,同时也方便小伙伴们自己扩展,所以功能是很简洁的,只有一个插槽可以自定义展开收起按钮,传参数需要传入文本、最多超出行数。

1.确定参数和变量

要实现这样一个组件,要知道我们需要哪些参数和辅助方法:

1.所需传入参数:文本(text)、最多展示行数(maxLines)、展开收起按钮() 2.组件内参数:文本截取长度(offset)、当前文本是否为展开状态(expanded)、实际展示文本(realText) 3.辅助方法:获取当前文本长度(getLines)、判断当前文本是否会超过最大行数(isOverflow)、计算文本截取长度(calculateOffset)。 2.实现核心功能

这个组件最核心的部分就是需要拿到每行文本的宽度,根据我们按钮的大小和最后一行的宽度来判断是否可以显示的下: 组件的dom结构如下:

// TextOverflow.vue {{ realText }} export default { props: { text: '', maxLines: 3, }, data() { return { offset: this.text.length, expanded: false, slotBoxWidth: 0, }; }, computed: { // 返回展示的文案 realText() {}, }, methods: { calculateOffset() { // 计算截取位置 }, isOverflow() { // 判断是否超出最大行数 }, getLines() { // 获取当前文本行数 } }, mounted() { // 计算插槽内容宽度 this.slotBoxWidth = this.$refs.slotRef.clientWidth; this.calculateOffset(); }, }; 复制代码

在组件渲染完成后(mounted),先计算出插槽内容的宽度slotBoxWidth;然后开始计算文案需要截取的长度: 如果文本已经超出最大行数(this.isOverflow()), 那么需截取的长度(offset)应减1后继续计算是否文本已经超出…………,不断递归直到this.isOverflow() ==== false

calculateOffset() { this.$nextTick(() => { if (this.isOverflow()) { this.offset--; this.calculateOffset(); } }); }, 复制代码

实现文本是否超出的逻辑判断:

获取最后一行的宽度lastWidth: 如果当前文本行数this.getLines()已经为最大行数且最后一行的宽度 + 插槽内容宽度 > 300 (文本总宽度),说明当前文本已经超出最大限制; 或者文本行数this.getLines()已经大于最大行数也说明超出最大限制:

isOverflow() { if (this.maxLines) { const lastWidth = this.$refs.overEllipsis.getClientRects()[ this.maxLines - 1 ].width; const lastLineOver = !!( this.getLines() === this.maxLines && lastWidth + this.slotBoxWidth > 300 ); if (this.getLines() > this.maxLines || lastLineOver) { return true; } } return false; }, 复制代码

获取文本行数: 直接使用getClientRects获取即可:

getLines() { return this.$refs.overEllipsis.getClientRects().length; }, 复制代码

获取需要展示的文案: 判断文本是否已经被截取(this.offset !== this.text.length,截取位置为文本长度即初始状态),如果已经被截取,则返回截取后的字符串 + 省略号:this.text.slice(0, this.offset) + "..."

realText() { // 是否被截取 const isCutOut = this.offset !== this.text.length; let realText = this.text; if (isCutOut) { realText = this.text.slice(0, this.offset) + "..."; } return realText; }, 复制代码

效果展示: 使用一下我们新写的组件,传入一个按钮:

展开 复制代码

到这里最核心的逻辑,判断超出部分省略已经实现了

3.完善组件功能

最核心的计算逻辑实现了,下面要完成其他的功能: 添加展开收起点击事件

这个涉及到插槽传参的问题,需要在上传入我们需要的参数,然后在父组件内接收这些参数:

// TextOverflow.vue // methods: toggle() { this.expanded = !this.expanded; } // App.vue {{ expanded ? "收起" : "展开" }} 复制代码

在插槽上提供了两个参数click-toggle(点击事件)和expanded(文本是否展开),这样我们就实现了按钮点击实现展开收起的逻辑。 添加参数设置最大文本宽度 首先我们先把之前写死的文本宽度替换为动态计算,即直接获取组件宽度(textBoxWidth):

... mounted() { this.slotBoxWidth = this.$refs.slotRef.clientWidth; this.textBoxWidth = this.$refs.textOverflow.clientWidth; this.calculateOffset(); }, 复制代码

虽然我们在组件外可以加一个元素来固定组件内宽度:

{{ expanded ? "收起" : "展开" }} .box { width: 300px; } 复制代码

但添加一个width来直接设置宽度感觉使用更加方便和灵活,所以:

{{ expanded ? "收起" : "展开" }} // TextOverflow.vue ... export default { props: { width: { type: Number, default: 0 } }, computed: { boxStyle() { if (this.width) { return { width: this.width + 'px' } } }, } } 复制代码 四、优化组件

前面已经实现了组件全部功能,但在一些处理上不是很优雅,比如在递归去计算文案截取长度时(calculateOffset):

1.二分法优化计算次数

之前逻辑是判断一次截取一个文本,再判断一次截取一个,这样如果我们一行文本特别多,那么就会这样循环很多次,显然这里是可以优化的,我这里使用的优化方案就是二分法:

为了尽可能减少函数递归的次数,我们要先明白使用二分法优化这部分的思路是什么: 为了方便理解,假设text为长度是100的文案,显示则需截取前65个文案 1.先进行全部文案的计算,那么第一次计算,isOverflow为true,已经超出最大行数限制,这时计算到我们中间的位置为50; 2.此时已经知道长度为100已经超出,那么我们在截取到50,再计算isOverflow为false,那么就知道:应该截取的位置肯定是在[50, 100]之间,然后我们再计算到中间位置75 ((50 + 100)/ 2) 3.截取到75位置,计算isOverflow为true,因为我们假设需要截取文案为65,大于65说明超出最大限制,那么可以知道应该截取的位置是在[50, 75]之间,再计算到中间位置62 Math.floor((50 + 75) / 2) ... 这样继续不断计算判断是否超出。 最后还有一个关键因素是需要什么时候停止递归:即我们计算的范围差值已经小于等于 1。

思路已经明确,下面就之间看代码实现:

calculateOffset(from, to) { this.$nextTick(() => { if (Math.abs(from - to) this.maxLines) { this.showSlotNode = true this.$nextTick(() => { this.slotBoxWidth = this.$refs.slotRef.clientWidth; this.textBoxWidth = this.$refs.textOverflow.clientWidth; this.calculateOffset(0, this.text.length); }) } } 复制代码 五、完整代码

因为前面代码都是提取对应功能部分的代码,如果不便阅读或复制,这里直接贴上完整的组件代码:

{{ realText }} export default { props: { text: { type: String, default: "", }, maxLines: { type: Number, default: 3, }, width: { type: Number, default: 0, }, }, data() { return { offset: this.text.length, expanded: false, slotBoxWidth: 0, textBoxWidth: this.width, showSlotNode: false }; }, computed: { boxStyle() { if (this.width) { return { width: this.width + "px", }; } }, realText() { // 是否被截取 const isCutOut = this.offset !== this.text.length; let realText = this.text; if (isCutOut && !this.expanded) { realText = this.text.slice(0, this.offset) + "..."; } return realText; }, }, methods: { calculateOffset(from, to) { this.$nextTick(() => { if (Math.abs(from - to) 最大行数 或则 已经是最大行数但最后一行宽度 + 后面内容超出正常宽度 const lastLineOver = !!( len === this.maxLines && lastWidth + this.slotBoxWidth > this.textBoxWidth ); if (len > this.maxLines || lastLineOver) { return true; } } return false; }, getLines() { const clientRects = this.$refs.overEllipsis.getClientRects(); return { len: clientRects.length, lastWidth: clientRects[clientRects.length - 1].width, }; }, toggle() { this.expanded = !this.expanded; }, }, mounted() { const { len } = this.getLines() if (len > this.maxLines) { this.showSlotNode = true this.$nextTick(() => { this.slotBoxWidth = this.$refs.slotRef.clientWidth; this.textBoxWidth = this.$refs.textOverflow.clientWidth; this.calculateOffset(0, this.text.length); }) } }, }; .slot-box { display: inline-block; } 复制代码

组件使用方式

{{ expanded ? "收起" : "展开" }} 复制代码 六、结束

这个组件我是参考了第三方库vue-clamp,因为之前遇到过类似的需求,所以看到这个库后也大概看了他的实现方式。通过阅读源码了解到getClientRects和使用二分法来优化递归次数。由于时间和能力有限,并没有对源码做更深入的解读,所以只能用自己熟悉的知识实现了一个简化版组件,也算是分享一下自己的思路吧。如果有对这部分感兴趣同学也可以一起研究,共同进步。

感谢阅读🙏



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3